Implement overrides via local cargo configuration
authorAlex Crichton <alex@alexcrichton.com>
Fri, 31 Oct 2014 19:25:40 +0000 (12:25 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Wed, 5 Nov 2014 19:37:34 +0000 (11:37 -0800)
This is an implementation of overriding native dependencies and passing
aribtrary metadata and such.

src/cargo/ops/cargo_clean.rs
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_rustc/context.rs
src/cargo/ops/cargo_rustc/custom_build.rs
src/cargo/ops/cargo_rustc/mod.rs
src/cargo/ops/mod.rs
tests/test_cargo_compile_custom_build.rs

index 0560225a3818b623468fcfd4997c091d463dd77a..7b3aa0ae6df567cccf86d76087cc63c0f049631c 100644 (file)
@@ -1,3 +1,4 @@
+use std::collections::HashMap;
 use std::io::fs::{mod, PathExtensions};
 
 use core::{MultiShell, PackageSet};
@@ -49,7 +50,7 @@ pub fn clean(manifest_path: &Path, opts: &mut CleanOptions) -> CargoResult<()> {
     let pkgs = PackageSet::new([]);
     let cx = try!(Context::new("compile", &resolve, &srcs, &pkgs, &mut cfg,
                                Layout::at(root.get_absolute_target_dir()),
-                               None, &pkg));
+                               None, &pkg, HashMap::new()));
 
     // And finally, clean everything out!
     for target in pkg.get_targets().iter() {
index 2947efa4b563f39c057198521a16ca1c82d49c6c..8b25881fe25fa515865970ab43bf833b01a3819f 100644 (file)
@@ -28,7 +28,7 @@ use std::collections::HashMap;
 use core::registry::PackageRegistry;
 use core::{MultiShell, Source, SourceId, PackageSet, Package, Target, PackageId};
 use core::resolver;
-use ops;
+use ops::{mod, BuildOutput};
 use sources::{PathSource};
 use util::config::{Config, ConfigValue};
 use util::{CargoResult, Wrap, config, internal, human, ChainError, profile};
@@ -138,12 +138,12 @@ pub fn compile_pkg(package: &Package, options: &mut CompileOptions)
 
     let ret = {
         let _p = profile::start("compiling");
-        try!(scrape_target_config(&config, &user_configs));
+        let lib_overrides = try!(scrape_target_config(&config, &user_configs));
 
         try!(ops::compile_targets(env.as_slice(), targets.as_slice(), to_build,
                                   &PackageSet::new(packages.as_slice()),
                                   &resolve_with_overrides, &sources,
-                                  &config))
+                                  &config, lib_overrides))
     };
 
     return Ok(ret);
@@ -175,41 +175,68 @@ fn source_ids_from_config(configs: &HashMap<String, config::ConfigValue>,
 
 fn scrape_target_config(config: &Config,
                         configs: &HashMap<String, config::ConfigValue>)
-                        -> CargoResult<()> {
+                        -> CargoResult<HashMap<String, BuildOutput>> {
     let target = match configs.find_equiv("target") {
-        None => return Ok(()),
+        None => return Ok(HashMap::new()),
         Some(target) => try!(target.table().chain_error(|| {
             internal("invalid configuration for the key `target`")
         })),
     };
-    let target = match config.target() {
-        None => target,
-        Some(triple) => match target.find_equiv(triple) {
-            None => return Ok(()),
-            Some(target) => try!(target.table().chain_error(|| {
-                internal(format!("invalid configuration for the key \
-                                  `target.{}`", triple))
-            })),
-        },
+    let triple = config.target().unwrap_or(config.rustc_host()).to_string();
+    let target = match target.find(&triple) {
+        None => return Ok(HashMap::new()),
+        Some(target) => try!(target.table().chain_error(|| {
+            internal(format!("invalid configuration for the key \
+                              `target.{}`", triple))
+        })),
     };
 
-    match target.find_equiv("ar") {
-        None => {}
-        Some(ar) => {
-            config.set_ar(try!(ar.string().chain_error(|| {
-                internal("invalid configuration for key `ar`")
-            })).ref0().to_string());
-        }
-    }
-
-    match target.find_equiv("linker") {
-        None => {}
-        Some(linker) => {
-            config.set_linker(try!(linker.string().chain_error(|| {
-                internal("invalid configuration for key `ar`")
-            })).ref0().to_string());
+    let mut ret = HashMap::new();
+    for (k, v) in target.iter() {
+        match k.as_slice() {
+            "ar" | "linker" => {
+                let v = try!(v.string().chain_error(|| {
+                    internal(format!("invalid configuration for key `{}`", k))
+                })).ref0().to_string();
+                if k.as_slice() == "linker" {
+                    config.set_linker(v);
+                } else {
+                    config.set_ar(v);
+                }
+            }
+            lib_name => {
+                let table = try!(v.table().chain_error(|| {
+                    internal(format!("invalid configuration for the key \
+                                      `target.{}.{}`", triple, lib_name))
+                }));
+                let mut output = BuildOutput {
+                    library_paths: Vec::new(),
+                    library_links: Vec::new(),
+                    metadata: Vec::new(),
+                };
+                for (k, v) in table.iter() {
+                    let v = try!(v.string().chain_error(|| {
+                        internal(format!("invalid configuration for the key \
+                                          `target.{}.{}.{}`", triple, lib_name,
+                                          k))
+                    })).val0();
+                    if k.as_slice() == "rustc-flags" {
+                        let whence = format!("in `target.{}.{}.rustc-flags`",
+                                             triple, lib_name);
+                        let whence = whence.as_slice();
+                        let (paths, links) = try!(
+                            BuildOutput::parse_rustc_flags(v.as_slice(), whence)
+                        );
+                        output.library_paths.extend(paths.into_iter());
+                        output.library_links.extend(links.into_iter());
+                    } else {
+                        output.metadata.push((k.to_string(), v.to_string()));
+                    }
+                }
+                ret.insert(lib_name.to_string(), output);
+            }
         }
     }
 
-    Ok(())
+    Ok(ret)
 }
index 4b25688a8ae71b732240c3d666504b7523297169..49b1c63e41f2ebf98db489b89c36722e8e08c3ac 100644 (file)
@@ -1,12 +1,13 @@
 use std::collections::HashSet;
 use std::collections::hash_map::{HashMap, Occupied, Vacant};
 use std::str;
+use std::sync::{Arc, Mutex};
 
 use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target};
 use util::{mod, CargoResult, ChainError, internal, Config, profile};
 use util::human;
 
-use super::{Kind, KindHost, KindTarget, Compilation};
+use super::{Kind, KindHost, KindTarget, Compilation, BuildOutput};
 use super::layout::{Layout, LayoutProxy};
 
 #[deriving(Show)]
@@ -21,6 +22,7 @@ pub struct Context<'a, 'b: 'a> {
     pub resolve: &'a Resolve,
     pub sources: &'a SourceMap<'b>,
     pub compilation: Compilation,
+    pub native_libs: Arc<Mutex<HashMap<String, BuildOutput>>>,
 
     env: &'a str,
     host: Layout,
@@ -37,7 +39,8 @@ impl<'a, 'b: 'a> Context<'a, 'b> {
     pub fn new(env: &'a str, resolve: &'a Resolve, sources: &'a SourceMap<'b>,
                deps: &'a PackageSet, config: &'b Config<'b>,
                host: Layout, target: Option<Layout>,
-               root_pkg: &Package)
+               root_pkg: &Package,
+               native_libs: HashMap<String, BuildOutput>)
                -> CargoResult<Context<'a, 'b>> {
         let (target_dylib, target_exe) =
                 try!(Context::filename_parts(config.target()));
@@ -63,6 +66,7 @@ impl<'a, 'b: 'a> Context<'a, 'b> {
             host_dylib: host_dylib,
             requirements: HashMap::new(),
             compilation: Compilation::new(root_pkg),
+            native_libs: Arc::new(Mutex::new(native_libs)),
         })
     }
 
@@ -230,12 +234,10 @@ impl<'a, 'b: 'a> Context<'a, 'b> {
             None => return vec!(),
             Some(deps) => deps,
         };
-        deps.map(|pkg_id| self.get_package(pkg_id))
-        .filter_map(|pkg| {
+        deps.map(|pkg_id| self.get_package(pkg_id)).filter_map(|pkg| {
             pkg.get_targets().iter().find(|&t| self.is_relevant_target(t))
                .map(|t| (pkg, t))
-        })
-        .collect()
+        }).collect()
     }
 
     /// Gets a package for the given package id.
index 55c02232720e0e88117808a76bb3d8f41fea0a84..fa63736424376591c1c4b3d220d36435ccf7e460 100644 (file)
@@ -1,5 +1,5 @@
-use std::io::{fs, BufferedReader, BufReader, USER_RWX};
-use std::io::fs::{File, PathExtensions};
+use std::io::{fs, BufReader, USER_RWX};
+use std::io::fs::PathExtensions;
 
 use core::{Package, Target};
 use util::{CargoResult, CargoError, human};
@@ -8,10 +8,21 @@ use util::{internal, ChainError};
 use super::job::Work;
 use super::{process, KindHost, Context};
 
+/// Contains the parsed output of a custom build script.
+#[deriving(Clone)]
+pub struct BuildOutput {
+    /// Paths to pass to rustc with the `-L` flag
+    pub library_paths: Vec<Path>,
+    /// Names and link kinds of libraries, suitable for the `-l` flag
+    pub library_links: Vec<String>,
+    /// Metadata to pass to the immediate dependencies
+    pub metadata: Vec<(String, String)>,
+}
+
 /// Prepares a `Work` that executes the target as a custom build script.
 pub fn prepare_execute_custom_build(pkg: &Package, target: &Target,
-                                cx: &mut Context)
-                                -> CargoResult<Work> {
+                                    cx: &mut Context)
+                                    -> CargoResult<Work> {
     let layout = cx.layout(pkg, KindHost);
     let script_output = layout.build(pkg);
     let build_output = layout.build_out(pkg);
@@ -35,62 +46,50 @@ pub fn prepare_execute_custom_build(pkg: &Package, target: &Target,
     match cx.resolve.features(pkg.get_package_id()) {
         Some(features) => {
             for feat in features.iter() {
-                let feat = feat.as_slice().chars()
-                               .map(|c| c.to_uppercase())
-                               .map(|c| if c == '-' {'_'} else {c})
-                               .collect::<String>();
-                p = p.env(format!("CARGO_FEATURE_{}", feat).as_slice(), Some("1"));
+                p = p.env(format!("CARGO_FEATURE_{}",
+                                  super::envify(feat.as_slice())).as_slice(),
+                          Some("1"));
             }
         }
         None => {}
     }
 
-    // building the list of all possible `build/$pkg/output` files
-    // whether they exist or not will be checked during the work
-    let command_output_files = {
-        let layout = cx.layout(pkg, KindHost);
-        cx.dep_targets(pkg).iter().map(|&(pkg, _)| {
-            layout.build(pkg).join("output")
-        }).collect::<Vec<_>>()
+    // Gather the set of native dependencies that this package has
+    let lib_deps = {
+        cx.dep_targets(pkg).iter().filter_map(|&(pkg, _)| {
+            pkg.get_manifest().get_links()
+        }).map(|s| s.to_string()).collect::<Vec<_>>()
     };
 
+    let native_libs = cx.native_libs.clone();
+
     // Building command
     let pkg = pkg.to_string();
     let work = proc(desc_tx: Sender<String>) {
-        desc_tx.send_opt(build_output.display().to_string()).ok();
 
         if !build_output.exists() {
-            try!(fs::mkdir(&build_output, USER_RWX)
-                .chain_error(|| {
-                    internal("failed to create build output directory for build command")
-                }))
+            try!(fs::mkdir(&build_output, USER_RWX).chain_error(|| {
+                internal("failed to create build output directory for \
+                          build command")
+            }))
         }
 
-        // loading each possible custom build output file in order to get their metadata
-        let _metadata = {
-            let mut metadata = Vec::new();
-
-            for flags_file in command_output_files.into_iter() {
-                match File::open(&flags_file) {
-                    Ok(flags) => {
-                        let flags = try!(CustomBuildCommandOutput::parse(
-                            BufferedReader::new(flags), pkg.as_slice()));
-                        metadata.extend(flags.metadata.into_iter());
-                    },
-                    Err(_) => ()  // the file doesn't exist, probably means that this pkg
-                                  // doesn't have a build command
+        // loading each possible custom build output file in order to get their
+        // metadata
+        let mut p = p;
+        {
+            let native_libs = native_libs.lock();
+            for dep in lib_deps.iter() {
+                for &(ref key, ref value) in (*native_libs)[*dep].metadata.iter() {
+                    p = p.env(format!("DEP_{}_{}",
+                                      super::envify(dep.as_slice()),
+                                      super::envify(key.as_slice())).as_slice(),
+                              Some(value.as_slice()));
                 }
             }
+        }
 
-            metadata
-        };
-
-        // TODO: ENABLE THIS CODE WHEN `links` IS ADDED
-        /*let mut p = p;
-        for (key, value) in metadata.into_iter() {
-            p = p.env(format!("DEP_{}_{}", PUT LINKS VALUES HERE, value), value);
-        }*/
-
+        desc_tx.send_opt(p.to_string()).ok();
         let output = try!(p.exec_with_output().map_err(|mut e| {
             e.msg = format!("Failed to run custom build command for `{}`\n{}",
                             pkg, e.msg);
@@ -98,7 +97,7 @@ pub fn prepare_execute_custom_build(pkg: &Package, target: &Target,
         }));
 
         // parsing the output of the custom build script to check that it's correct
-        try!(CustomBuildCommandOutput::parse(BufReader::new(output.output.as_slice()),
+        try!(BuildOutput::parse(BufReader::new(output.output.as_slice()),
                                              pkg.as_slice()));
 
         // writing the output to the right directory
@@ -113,23 +112,14 @@ pub fn prepare_execute_custom_build(pkg: &Package, target: &Target,
     Ok(work)
 }
 
-/// Contains the parsed output of a custom build script.
-pub struct CustomBuildCommandOutput {
-    /// Paths to pass to rustc with the `-L` flag
-    pub library_paths: Vec<Path>,
-    /// Names and link kinds of libraries, suitable for the `-l` flag
-    pub library_links: Vec<String>,
-    /// Metadata to pass to the immediate dependencies
-    pub metadata: Vec<(String, String)>,
-}
-
-impl CustomBuildCommandOutput {
+impl BuildOutput {
     // Parses the output of a script.
     // The `pkg_name` is used for error messages.
-    pub fn parse<B: Buffer>(mut input: B, pkg_name: &str) -> CargoResult<CustomBuildCommandOutput> {
+    pub fn parse<B: Buffer>(mut input: B, pkg_name: &str) -> CargoResult<BuildOutput> {
         let mut library_paths = Vec::new();
         let mut library_links = Vec::new();
         let mut metadata = Vec::new();
+        let whence = format!("build script of `{}`", pkg_name);
 
         for line in input.lines() {
             // unwrapping the IoResult
@@ -153,46 +143,59 @@ impl CustomBuildCommandOutput {
             let (key, value) = match (key, value) {
                 (Some(a), Some(b)) => (a, b),
                 // line started with `cargo:` but didn't match `key=value`
-                _ => return Err(human(format!("Wrong output for the custom\
-                                               build script of `{}`:\n`{}`", pkg_name, line)))
+                _ => return Err(human(format!("Wrong output in {}: `{}`",
+                                              whence, line)))
             };
 
             if key == "rustc-flags" {
-                // TODO: some arguments (like paths) may contain spaces
-                let mut flags_iter = value.words();
-                loop {
-                    let flag = match flags_iter.next() {
-                        Some(f) => f,
-                        None => break
-                    };
-                    if flag != "-l" && flag != "-L" {
-                        return Err(human(format!("Only `-l` and `-L` flags are allowed \
-                                                 in build script of `{}`:\n`{}`",
-                                                 pkg_name, value)))
-                    }
-                    let value = match flags_iter.next() {
-                        Some(v) => v,
-                        None => return Err(human(format!("Flag in rustc-flags has no value\
-                                                          in build script of `{}`:\n`{}`",
-                                                          pkg_name, value)))
-                    };
-                    match flag {
-                        "-l" => library_links.push(value.to_string()),
-                        "-L" => library_paths.push(Path::new(value)),
-
-                        // was already checked above
-                        _ => return Err(human("only -l and -L flags are allowed"))
-                    };
-                }
+                let whence = whence.as_slice();
+                let (libs, links) = try!(
+                    BuildOutput::parse_rustc_flags(value, whence)
+                );
+                library_links.extend(links.into_iter());
+                library_paths.extend(libs.into_iter());
             } else {
                 metadata.push((key.to_string(), value.to_string()))
             }
         }
 
-        Ok(CustomBuildCommandOutput {
+        Ok(BuildOutput {
             library_paths: library_paths,
             library_links: library_links,
             metadata: metadata,
         })
     }
+
+    pub fn parse_rustc_flags(value: &str, whence: &str)
+                             -> CargoResult<(Vec<Path>, Vec<String>)> {
+        // TODO: some arguments (like paths) may contain spaces
+        let value = value.trim();
+        let mut flags_iter = value.words();
+        let (mut library_links, mut library_paths) = (Vec::new(), Vec::new());
+        loop {
+            let flag = match flags_iter.next() {
+                Some(f) => f,
+                None => break
+            };
+            if flag != "-l" && flag != "-L" {
+                return Err(human(format!("Only `-l` and `-L` flags are allowed \
+                                         in {}: `{}`",
+                                         whence, value)))
+            }
+            let value = match flags_iter.next() {
+                Some(v) => v,
+                None => return Err(human(format!("Flag in rustc-flags has no value\
+                                                  in {}: `{}`",
+                                                  whence, value)))
+            };
+            match flag {
+                "-l" => library_links.push(value.to_string()),
+                "-L" => library_paths.push(Path::new(value)),
+
+                // was already checked above
+                _ => return Err(human("only -l and -L flags are allowed"))
+            };
+        }
+        Ok((library_paths, library_links))
+    }
 }
index 4691fdc70a7c2b2a85e0fe3f0be11a3032439700..363ebd875482629ce588ed27fc7d6bd873db2920 100644 (file)
@@ -1,4 +1,4 @@
-use std::collections::HashSet;
+use std::collections::{HashSet, HashMap};
 use std::dynamic_lib::DynamicLibrary;
 use std::io::{fs, BufferedReader, USER_RWX};
 use std::io::fs::{File, PathExtensions};
@@ -8,7 +8,6 @@ use core::{SourceMap, Package, PackageId, PackageSet, Target, Resolve};
 use util::{mod, CargoResult, ProcessBuilder, CargoError, human, caused_human};
 use util::{Require, Config, internal, ChainError, Fresh, profile, join_paths};
 
-use self::custom_build::CustomBuildCommandOutput;
 use self::job::{Job, Work};
 use self::job_queue as jq;
 use self::job_queue::JobQueue;
@@ -18,6 +17,7 @@ pub use self::context::Context;
 pub use self::context::{PlatformPlugin, PlatformPluginAndTarget};
 pub use self::context::{PlatformRequirement, PlatformTarget};
 pub use self::layout::{Layout, LayoutProxy};
+pub use self::custom_build::BuildOutput;
 
 mod context;
 mod compilation;
@@ -76,7 +76,8 @@ fn uniq_target_dest<'a>(targets: &[&'a Target]) -> Option<&'a str> {
 pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package,
                            deps: &PackageSet, resolve: &'a Resolve,
                            sources: &'a SourceMap,
-                           config: &'a Config<'a>)
+                           config: &'a Config<'a>,
+                           lib_overrides: HashMap<String, BuildOutput>)
                            -> CargoResult<Compilation> {
     if targets.is_empty() {
         return Ok(Compilation::new(pkg))
@@ -94,7 +95,8 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package,
     });
 
     let mut cx = try!(Context::new(env, resolve, sources, deps, config,
-                                   host_layout, target_layout, pkg));
+                                   host_layout, target_layout, pkg,
+                                   lib_overrides));
     let mut queue = JobQueue::new(cx.resolve, deps, cx.config);
 
     // First ensure that the destination directory exists
@@ -305,11 +307,9 @@ fn compile_custom_old(pkg: &Package, cmd: &str,
     match cx.resolve.features(pkg.get_package_id()) {
         Some(features) => {
             for feat in features.iter() {
-                let feat = feat.as_slice().chars()
-                               .map(|c| c.to_uppercase())
-                               .map(|c| if c == '-' {'_'} else {c})
-                               .collect::<String>();
-                p = p.env(format!("CARGO_FEATURE_{}", feat).as_slice(), Some("1"));
+                p = p.env(format!("CARGO_FEATURE_{}",
+                                  envify(feat.as_slice())).as_slice(),
+                          Some("1"));
             }
         }
         None => {}
@@ -379,7 +379,7 @@ fn rustc(package: &Package, target: &Target,
             // list of `-l` flags to pass to rustc coming from custom build scripts
             let additional_library_links = match File::open(&command_output_file) {
                 Ok(f) => {
-                    let flags = try!(CustomBuildCommandOutput::parse(
+                    let flags = try!(BuildOutput::parse(
                         BufferedReader::new(f), name.as_slice()));
 
                     additional_library_paths.extend(flags.library_paths.iter().map(|p| p.clone()));
@@ -396,7 +396,7 @@ fn rustc(package: &Package, target: &Target,
                                         // doesn't have a build command
                 };
 
-                let flags = try!(CustomBuildCommandOutput::parse(
+                let flags = try!(BuildOutput::parse(
                     BufferedReader::new(flags), name.as_slice()));
                 additional_library_paths.extend(flags.library_paths.iter().map(|p| p.clone()));
             }
@@ -720,3 +720,10 @@ fn each_dep<'a>(pkg: &Package, cx: &'a Context, f: |&'a Package|) {
         }
     }
 }
+
+fn envify(s: &str) -> String {
+    s.chars()
+     .map(|c| c.to_uppercase())
+     .map(|c| if c == '-' {'_'} else {c})
+     .collect()
+}
index 33f165102a6a12a496e58c51f3dd7f962a33c710..d65629986d36b8ac5818d2d80635f805fe378ab7 100644 (file)
@@ -5,6 +5,7 @@ pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind, rustc_ve
 pub use self::cargo_rustc::{KindTarget, KindHost, Context, LayoutProxy};
 pub use self::cargo_rustc::{PlatformRequirement, PlatformTarget};
 pub use self::cargo_rustc::{PlatformPlugin, PlatformPluginAndTarget};
+pub use self::cargo_rustc::{BuildOutput};
 pub use self::cargo_run::run;
 pub use self::cargo_new::{new, NewOptions};
 pub use self::cargo_doc::{doc, DocOptions};
index db4613abd8d8de9e4bdf36007bec4cfd8df42488..9997051eda1f8085259991da81e2fa82d6a7164e 100644 (file)
@@ -129,9 +129,8 @@ test!(custom_build_script_wrong_rustc_flags {
     assert_that(p.cargo_process("build"),
                 execs().with_status(101)
                        .with_stderr(format!("\
-Only `-l` and `-L` flags are allowed in build script of `foo v0.5.0 ({})`:
-`-aaa -bbb
-`",
+Only `-l` and `-L` flags are allowed in build script of `foo v0.5.0 ({})`: \
+`-aaa -bbb`",
 p.url())));
 })
 
@@ -206,7 +205,6 @@ not have a custom build script
 "));
 })
 
-
 test!(links_duplicates {
     let p = project("foo")
         .file("Cargo.toml", r#"
@@ -244,3 +242,53 @@ linked to by one package
 "));
 })
 
+test!(overrides_and_links {
+    let (_, target) = ::cargo::ops::rustc_version().unwrap();
+
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.5.0"
+            authors = []
+            build = "build.rs"
+
+            [dependencies.a]
+            path = "a"
+        "#)
+        .file("src/lib.rs", "")
+        .file("build.rs", r#"
+            use std::os;
+            fn main() {
+                assert_eq!(os::getenv("DEP_FOO_FOO").unwrap().as_slice(), "bar");
+                assert_eq!(os::getenv("DEP_FOO_BAR").unwrap().as_slice(), "baz");
+            }
+        "#)
+        .file(".cargo/config", format!(r#"
+            [target.{}.foo]
+            rustc-flags = "-l foo -L bar"
+            foo = "bar"
+            bar = "baz"
+        "#, target).as_slice())
+        .file("a/Cargo.toml", r#"
+            [project]
+            name = "a"
+            version = "0.5.0"
+            authors = []
+            links = "foo"
+            build = "build.rs"
+        "#)
+        .file("a/src/lib.rs", "")
+        .file("a/build.rs", "not valid rust code");
+
+    assert_that(p.cargo_process("build").arg("-v"),
+                execs().with_status(0)
+                       .with_stdout("\
+Compiling a v0.5.0 (file://[..])
+  Running `rustc [..] --crate-name a [..]`
+Compiling foo v0.5.0 (file://[..])
+  Running `rustc build.rs [..]`
+  Running `rustc [..] --crate-name foo [..]`
+"));
+})
+